/*
	Sterownik karty sieciowej EDIMAX EU-4207 chipset Asix AX88772LF.
	Linux Mint 11. Kernel version 2.6.38-8.
*/

#include <linux/version.h>
#include <linux/module.h>
#include <linux/kmod.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/workqueue.h>
#include <linux/mii.h>
#include <linux/usb.h>
#include <linux/crc32.h>
#include "asix.h"
#include <linux/usb/usbnet.h>

/* Rzeczy przeznaczone do komunikacji ze sterownikiem za posrednictwem urzadzenia tekstowego w /dev/.
 * Opracowane na podstawie: http://linux.die.net/lkmpg/x569.html
 * http://linuxdevcenter.com/pub/a/linux/2007/07/05/devhelloworld-a-simple-introduction-to-device-drivers-under-linux.html?page=3 --bartek
 *
 * Na urządzenie tekstowe sterownik otrzymuje wszystkie ramki wysłane z wirtualnych interfejsów na tym węźle. Natomiast wysyła na to urządzenie
 * wszystkie ramki, które otrzymuje zarówno przez kabel jak i przez urządzenie tekstowe. */

#include <linux/fs.h> 

static int chardev_open = 0;

/* Sterownik w danej chwili przechowuje jedną ramkę. Trzeba przechować ją w zmiennej i zaznaczać
 * czy jakaś jest zapisana. Rozwiązanie nie jest thread safe! Niestety trzeba będzie zrobić jakiś większy
 * bufor, bo jądro nie działa wielowątkowo, a zbyt częste odmawianie zapisu jest niegrzeczne. :)*/
#define MTU 1500
char frame[MTU], *frame_end;

/* Funkcja wywoływana przy otwarciu pliku urządzenia znakowego:
 * Nie jestem do końca pewny, ale parametry tej funkcji służą do przekazania użytkownikowi
 * informacji na temat pliku urządzania (inode) i struktury samego pliku. W znalezionym przeze mnie
 * przykładzie były zignorowane więc pewnie tak można.
 *
 * Więcej o tych strukturach: http://tldp.org/LDP/tlk/ds/ds.html -- bartek */
static int chdev_open(struct inode *inode, struct file *file)
{
	if (chardev_open)
		return -EBUSY;

	chardev_open++;
	try_module_get(THIS_MODULE);

	return 0;
}

static int chdev_release(struct inode *inode, struct file *file)
{
    chardev_open--;	

	module_put(THIS_MODULE);

	return 0;
}

static ssize_t chdev_read(struct file *filp,	/* see include/linux/fs.h   */
			   char *buffer,	/* buffer to fill with data */
			   size_t length,	/* length of the buffer     */
			   loff_t * offset) /* TODO: number of bytes written? New offset? */
{
    size_t n;
    int i;

    if( frame_end == frame )
        return 0;

    n = frame_end - frame;
    if( length < n )
        return -EINVAL;

    for( i = 0; i < n; i++ )
    {
        buffer[i] = frame[i];
    }
    frame_end = frame;
    return n;
}

static ssize_t
chdev_write(struct file *filp, const char *buff, size_t len, loff_t * off)
{
    int i;
    if( len > MTU )
        return -MTU;

    if( frame_end != frame)
        return -EINVAL;
    frame_end = frame + len;

    for( i = 0; i < len; i++ )
    {
        frame[i] = buff[i];
    }
    return len;
}

static struct file_operations chardev_funcs = {
    .owner = THIS_MODULE,
    .read = chdev_read,
	.write = chdev_write,
	.open = chdev_open,
	.release = chdev_release
};

/* W necie widziałem ze trzy metody rejestrowania chardeviceów. WYdaje mi się że ten który tu stosuję jest aktualny,
 * bo jak patrzyłem w kod sterownik /dev/random to taka metoda była użyta. --Bartek */
static struct miscdevice chardev_mdev = {
        MISC_DYNAMIC_MINOR, /* Numer urzadzenia zostanie przydzielony dynamicznie */
        "vwlan", /* Nazwa urzadzenia */
        &chardev_funcs /* wskaznik na strukture z funkcjami obslugujacymi */
};

/* 
	Funkcja printk() stanowi odpowiednik funkcji printf przeznaczony do pracy z kodem zrodlowym jadra Linuksa. 
	Stworzono ja ze wzgledu na brak dostepu do standardowej biblioteki wejscia/wyjscia (stdio) w wielu miejscach jadra. 
	Jest podobna do printf pod wzgledem sposobu formatowania komunikatow. Nie obsluguje jednak liczb zmiennoprzecinkowych.
*/

#define axwarn(usbnet, fmt, arg...) \
	printk (KERN_WARNING "%s: " fmt "\n", (usbnet)->net->name, ##arg)

#define axerr(usbnet, fmt, arg...) \
	printk (KERN_ERR "%s: " fmt "\n", (usbnet)->net->name, ##arg)

#define DRV_VERSION	"3.5.0"

static char version[] = KERN_INFO "ASIX USB Ethernet Adapter:v" DRV_VERSION " " __TIME__ " " __DATE__ "\n" KERN_INFO "    http://www.asix.com.tw";

static void ax88772_link_reset (struct work_struct *work);

/* ASIX AX8817X based USB 2.0 Ethernet Devices */

/*	
	usbnet -> USB-to-network
	struct usbnet - interfejs usbnet dla kazdego sieciowego USB polaczonego uchwytem (handle)
*/

static int ax8817x_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, u16 size, void *data)
{
	/* 
	 	usb_control_msg -> Wysylanie wiadomosci kontrolnych do urzadzenia
	*/	 
	return usb_control_msg(
		dev->udev, /* struktura usb_device */
		usb_rcvctrlpipe(dev->udev, 0), /* makro - tworzy pipe dla usb_device o endpoincie 0 */
		cmd,
		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, /* kierunek usb (do hosta) | typ usb (dostawca) | odbiorca usb (urzadzenie) */
		value,
		index,
		data,
		size,
		USB_CTRL_GET_TIMEOUT); /* timeout uzywany do wysylania/odbiernia wiadomosci kontrolnych */
}

static int ax8817x_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, u16 size, void *data)
{
	/* 
	 	usb_control_msg -> Wysylanie wiadomosci kontrolnych do urzadzenia
		buduje URB kontrolny, wysyla go i czeka na zakonczenie
		
	 	wiecej : http://www.compsoc.man.ac.uk/~moz/kernelnewbies/documents/kdoc/kernel-api/r11386.html
	*/	 
	return usb_control_msg(
		dev->udev, /* struktura usb_device */
		usb_sndctrlpipe(dev->udev, 0), /* typ zapytania, makro - tworzy pipe dla usb_device o endpoincie 0 */
		cmd, /* zapytanie */
		USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, /* typ zapytania : kierunek usb (do urzadzenia) | typ usb (dostawca) | odbiorca usb (host) */
		value,
		index,
		data,
		size,
		USB_CTRL_SET_TIMEOUT); /* timeout uzywany do wysylania/odbiernia wiadomosci kontrolnych */
}

/* 
	USB Request Block - zawiera informacje potrzebne do wykonania transakcji USB, dostarczenia danych i odebranie statusu
	struktura urb (linie 42-76): http://www.mjmwired.net/kernel/Documentation/usb/URB.txt

    *_async_cmd_callback() to funckja odbierająca wynik asynchronicznego
    wywołania komendy. Patrz *_write_cmd_async().
*/

static void ax8817x_async_cmd_callback(struct urb *urb) 
{
	struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)urb->context;

	if (urb->status < 0) 
		printk(KERN_DEBUG "ax8817x_async_cmd_callback() failed with %d", urb->status);

	kfree(req);
	usb_free_urb(urb);
}

static int ax8817x_set_mac_addr (struct net_device *net, void *p)
{
	/* 
		netdev_priv pobiera dane prywatne urzadzenia sieciowego, wychodzi na to ze w tym polu sieci usbnet
		struct usbnet - interfejs z usbnet core do kazdego USB lacza sieciowego ktore obslugujemy
	*/
	struct usbnet *dev = netdev_priv(net); 
		
	/* 
		sockaddr przechowuje : rodzina adresu (np IF_NET) i adres protokolu 
	*/
	struct sockaddr *addr = p; 
	
	/* 
		kopiuje 6 bajtow(dlugosc adresu MAC) z sa_data do pola adresu urzadzenia 
		wiecej o net_device -> http://tomoyo.sourceforge.jp/cgi-bin/lxr/source/include/linux/netdevice.h?a=sparc#L958
	*/
	/* Ustawianie MACu na pałę. Chyba lepiej podać jakiś prawdziwy, bo jak dałem
	z dupy to nie działało. Nie jestem pewny co do kolejności bajtów, zatem
	jak skompilujesz, sprawdź czy ustawiło MAC na 00:24:81:56:FC:6C ;P 
	void *buf;
	buf = kmalloc(6, GFP_KERNEL);
	((char *)buf)[0] = 0x00;
	((char *)buf)[1] = 0x50;
	((char *)buf)[2] = 0xB6;
	((char *)buf)[3] = 0x4E;
	((char *)buf)[4] = 0x54;
	((char *)buf)[5] = 0x9C;
	memcpy(net->dev_addr, buf, ETH_ALEN);*/
	memcpy (net->dev_addr, addr->sa_data, ETH_ALEN); 	

	return ax8817x_write_cmd (dev, AX88772_CMD_WRITE_NODE_ID, 0, 0, ETH_ALEN, net->dev_addr);
}

static void ax88772_status(struct usbnet *dev, struct urb *urb)
{
	struct ax88172_int_data *event;
	struct ax8817x_data *data = (struct ax8817x_data *)&dev->data;
	struct ax88772_data *ax772_data = data->priv.ax772_data;
	int link;
	
	if (urb->actual_length < 8)
		return;

	event = urb->transfer_buffer;
	link = event->link & 0x01;
	
	if (netif_carrier_ok(dev->net) != link) 
	{
		if (link) 
		{
			netif_carrier_on(dev->net);
			ax772_data->Event = AX_SET_RX_CFG;
		} 
		else 
		{
			netif_carrier_off(dev->net);
			if (ax772_data->Event == AX_NOP) 
			{
				ax772_data->Event = PHY_POWER_DOWN;
				ax772_data->TickToExpire = 25;
			}
		}

		axwarn(dev, "ax8817x - Link Status is: %d", link);
	}
	
	if (ax772_data->Event)
		queue_work (ax772_data->ax_work, &ax772_data->check_link);
}

/*
    Asynchroniczne wywołanie komendy cmd na urządzeniu dev. Tworzy strukturę URB,
    która zawiera cały opis tego co wywołać na jakim urządzeniu i jaka funkcja
    odbierze wynik: _async_cmd_callback() */

static void ax8817x_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value, u16 index, u16 size, void *data)
{
	struct usb_ctrlrequest *req;
	int status;
	struct urb *urb;

	if ((urb = usb_alloc_urb(0, GFP_ATOMIC)) == NULL) 
	{
		axerr(dev, "Error allocating URB in write_cmd_async!");
		return;
	}

	if ((req = kmalloc (sizeof (struct usb_ctrlrequest), GFP_ATOMIC)) == NULL) 
	{
		axerr(dev, "Failed to allocate memory for control request");
		usb_free_urb(urb);
		return;
	}

	req->bRequestType = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE;
	req->bRequest = cmd;
	req->wValue = cpu_to_le16(value);
	req->wIndex = cpu_to_le16(index);
	req->wLength = cpu_to_le16(size);

	usb_fill_control_urb(urb, dev->udev,
			     usb_sndctrlpipe(dev->udev, 0),
			     (void *)req, data, size,
			     ax8817x_async_cmd_callback, req);

	if((status = usb_submit_urb(urb, GFP_ATOMIC)) < 0) 
	{
		axerr(dev, "Error submitting the control message: status=%d", status);
		kfree(req);
		usb_free_urb(urb);
	}
}

static void ax8817x_set_multicast(struct net_device *net)
{
	struct usbnet *dev = netdev_priv(net);
	struct ax8817x_data *data = (struct ax8817x_data *)&dev->data;
	u8 rx_ctl = AX_RX_CTL_START | AX_RX_CTL_AB;
	int mc_count;

	mc_count = netdev_mc_count (net);

	if (net->flags & IFF_PROMISC)
		rx_ctl |= AX_RX_CTL_PRO;
	else if (net->flags & IFF_ALLMULTI || mc_count > AX_MAX_MCAST) 
		rx_ctl |= AX_RX_CTL_AMALL;
 	else if (mc_count == 0) 
		{ } /* just broadcast and directed */
	else 
	{
		/* We use the 20 byte dev->data
		 * for our 8 byte filter buffer
		 * to avoid allocating memory that
		 * is tricky to free later */
		u32 crc_bits;

		struct netdev_hw_addr *ha;
		memset(data->multi_filter, 0, AX_MCAST_FILTER_SIZE);
		netdev_for_each_mc_addr (ha, net) 
		{
			crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26;
			data->multi_filter[crc_bits >> 3] |= 1 << (crc_bits & 7);
		}

		ax8817x_write_cmd_async(dev, AX_CMD_WRITE_MULTI_FILTER, 0, 0, AX_MCAST_FILTER_SIZE, data->multi_filter);

		rx_ctl |= AX_RX_CTL_AM;
	}

	ax8817x_write_cmd_async(dev, AX_CMD_WRITE_RX_CTL, rx_ctl, 0, 0, NULL);
}

static int ax8817x_mdio_read(struct net_device *netdev, int phy_id, int loc)
{
	struct usbnet *dev = netdev_priv(netdev);
	u16 res;
	u8 buf[1];

	ax8817x_write_cmd(dev, AX_CMD_SET_SW_MII, 0, 0, 0, &buf);
	ax8817x_read_cmd(dev, AX_CMD_READ_MII_REG, phy_id, (__u16)loc, 2, (u16 *)&res);
	ax8817x_write_cmd(dev, AX_CMD_SET_HW_MII, 0, 0, 0, &buf);

	return res & 0xffff;
}

/* same as above, but converts resulting value to cpu byte order */
static int ax8817x_mdio_read_le(struct net_device *netdev, int phy_id, int loc)
{
	return le16_to_cpu(ax8817x_mdio_read(netdev,phy_id, loc));
}

static void ax8817x_mdio_write(struct net_device *netdev, int phy_id, int loc, int val)
{
	struct usbnet *dev = netdev_priv(netdev);
	u16 res = val;
	u8 buf[1];

	ax8817x_write_cmd(dev, AX_CMD_SET_SW_MII, 0, 0, 0, &buf);
	ax8817x_write_cmd(dev, AX_CMD_WRITE_MII_REG, phy_id, (__u16)loc, 2, (u16 *)&res);
	ax8817x_write_cmd(dev, AX_CMD_SET_HW_MII, 0, 0, 0, &buf);
}

/* same as above, but converts new value to le16 byte order before writing */
static void ax8817x_mdio_write_le(struct net_device *netdev, int phy_id, int loc, int val)
{
	ax8817x_mdio_write( netdev, phy_id, loc, cpu_to_le16(val) );
}

static int ax88772_suspend (struct usb_interface *intf, pm_message_t message)
{
	struct usbnet *dev = usb_get_intfdata(intf);
	u16 medium;

	ax8817x_read_cmd (dev, AX_CMD_READ_MEDIUM_MODE, 0, 0, 2, &medium);
	ax8817x_write_cmd (dev, AX_CMD_WRITE_MEDIUM_MODE, (medium & ~AX88772_MEDIUM_RX_ENABLE), 0, 0, NULL);

	return usbnet_suspend (intf, message);
}

static int ax88772_resume (struct usb_interface *intf)
{
	struct usbnet *dev = usb_get_intfdata(intf);

	netif_carrier_off (dev->net);

	return usbnet_resume (intf);
}

/* Najprawdopodobniej funkcja obługująca Wake on LAN (zabawka do włączania kompa
   przez sieć. Raczej się nie przyda. */
static void ax8817x_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo)
{
	struct usbnet *dev = netdev_priv(net);
	u8 opt;

	if (ax8817x_read_cmd(dev, AX_CMD_READ_MONITOR_MODE, 0, 0, 1, &opt) < 0) 
	{
		wolinfo->supported = 0;
		wolinfo->wolopts = 0;
		return;
	}
	wolinfo->supported = WAKE_PHY | WAKE_MAGIC;
	wolinfo->wolopts = 0;

	if (opt & AX_MONITOR_LINK)
		wolinfo->wolopts |= WAKE_PHY;
	if (opt & AX_MONITOR_MAGIC)
		wolinfo->wolopts |= WAKE_MAGIC;
}

/* j/w */
static int ax8817x_set_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo)
{
	struct usbnet *dev = netdev_priv(net);
	u8 opt = 0;

	if (wolinfo->wolopts & WAKE_PHY)
		opt |= AX_MONITOR_LINK;
	if (wolinfo->wolopts & WAKE_MAGIC)
		opt |= AX_MONITOR_MAGIC;

	if (ax8817x_write_cmd(dev, AX_CMD_WRITE_MONITOR_MODE, opt, 0, 0, NULL) < 0)
		return -EINVAL;

	return 0;
}

static int ax8817x_get_eeprom_len(struct net_device *net)
{
	return AX_EEPROM_LEN;
}

static int ax8817x_get_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, u8 *data)
{
	struct usbnet *dev = netdev_priv(net);
	u16 *ebuf = (u16 *)data;
	int i;

	/* Crude hack to ensure that we don't overwrite memory
	 * if an odd length is supplied
	 */
	if (eeprom->len % 2)
		return -EINVAL;

	eeprom->magic = AX_EEPROM_MAGIC;

	/* ax8817x returns 2 bytes from eeprom on read */
	for (i=0; i < eeprom->len / 2; i++) 
	{
		if (ax8817x_read_cmd(dev, AX_CMD_READ_EEPROM, eeprom->offset + i, 0, 2, &ebuf[i]) < 0)
			return -EINVAL;
	}
	return 0;
}

static void ax8817x_get_drvinfo (struct net_device *net, struct ethtool_drvinfo *info)
{
	/* Inherit standard device info */
	usbnet_get_drvinfo(net, info);
	info->eedump_len = 0x3e;
}

static int ax8817x_get_settings(struct net_device *net, struct ethtool_cmd *cmd)
{
	struct usbnet *dev = netdev_priv(net);
	return mii_ethtool_gset(&dev->mii,cmd);
}

static int ax8817x_set_settings(struct net_device *net, struct ethtool_cmd *cmd)
{
	struct usbnet *dev = netdev_priv(net);
	return mii_ethtool_sset(&dev->mii,cmd);
}

static int ax8817x_ioctl (struct net_device *net, struct ifreq *rq, int cmd)
{
	struct usbnet *dev = netdev_priv(net);
	return generic_mii_ioctl(&dev->mii, if_mii(rq), cmd, NULL);
}

static const struct net_device_ops ax88x72_netdev_ops = 
{
	.ndo_open		= usbnet_open,
	.ndo_stop		= usbnet_stop,
	.ndo_start_xmit		= usbnet_start_xmit,
	.ndo_tx_timeout		= usbnet_tx_timeout,
	.ndo_change_mtu		= usbnet_change_mtu,
	.ndo_set_mac_address 	= ax8817x_set_mac_addr,
	.ndo_validate_addr	= eth_validate_addr,
	.ndo_do_ioctl		= ax8817x_ioctl,
	.ndo_set_multicast_list = ax8817x_set_multicast,
};

static struct ethtool_ops ax88772_ethtool_ops = 
{
	.get_drvinfo		= ax8817x_get_drvinfo,
	.get_link		= ethtool_op_get_link,
	.get_msglevel		= usbnet_get_msglevel,
	.set_msglevel		= usbnet_set_msglevel,
	.get_wol		= ax8817x_get_wol,
	.set_wol		= ax8817x_set_wol,
	.get_eeprom_len		= ax8817x_get_eeprom_len,
	.get_eeprom		= ax8817x_get_eeprom,
	.get_settings		= ax8817x_get_settings,
	.set_settings		= ax8817x_set_settings,
};

static int ax88772_bind(struct usbnet *dev, struct usb_interface *intf)
{
	int ret;
	void *buf;
	struct ax8817x_data *data = (struct ax8817x_data *)&dev->data;
	struct ax88772_data *ax772_data = NULL;

	usbnet_get_endpoints(dev,intf);

	buf = kmalloc(6, GFP_KERNEL);
	if(!buf) 
	{
		axerr(dev, "Cannot allocate memory for buffer");
		ret = -ENOMEM;
		goto out1;
	}

        ax772_data = kmalloc (sizeof(*ax772_data), GFP_KERNEL);
        if (!ax772_data) 
	{
                axerr(dev, "Cannot allocate memory for AX88772 data");
                kfree (buf);
                return -ENOMEM;
        }
        memset (ax772_data, 0, sizeof(*ax772_data));
        data->priv.ax772_data = ax772_data;

        ax772_data->ax_work = create_singlethread_workqueue ("ax88772");
        if (!ax772_data->ax_work) 
	{
                kfree (ax772_data);
                kfree (buf);
                return -ENOMEM;
        }

        ax772_data->dev = dev;

        INIT_WORK (&ax772_data->check_link, ax88772_link_reset);

	/* reload eeprom data */
	if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_GPIOS, 0x00B0, 0, 0, buf)) < 0)
		goto out2;

	msleep(5);

	/* Initialize MII structure */
	dev->mii.dev = dev->net;
	dev->mii.mdio_read = ax8817x_mdio_read_le;
	dev->mii.mdio_write = ax8817x_mdio_write_le;
	dev->mii.phy_id_mask = 0xff;
	dev->mii.reg_num_mask = 0xff;

	/* Get the PHY id */
	if ((ret = ax8817x_read_cmd(dev, AX_CMD_READ_PHY_ID, 0, 0, 2, buf)) < 0) 
	{
		axerr(dev, "Error reading PHY ID: %02x", ret);
		goto out2;
	} 
	else if (ret < 2) 
	{
		/* this should always return 2 bytes */
		axerr(dev, "Read PHYID returned less than 2 bytes: ret=%02x", ret);
		ret = -EIO;
		goto out2;
	}
	dev->mii.phy_id = *((u8 *)buf + 1);

	if (dev->mii.phy_id == 0x10)
	{
		if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_PHY_SELECT, 0x0001, 0, 0, buf)) < 0) 
		{
			axerr(dev, "Select PHY #1 failed: %d", ret);
			goto out2;
		}

		if ((ret = ax8817x_write_cmd (dev, AX_CMD_SW_RESET, AX_SWRESET_IPPD, 0, 0, buf)) < 0) 
		{
			axerr(dev, "Failed to power down PHY: %d", ret);
			goto out2;
		}

		msleep(150);
		if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_CLEAR, 0, 0, buf)) < 0) 
		{
			axerr(dev, "Failed to perform software reset: %d", ret);
			goto out2;
		}

		msleep(150);
		if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_IPRL | AX_SWRESET_PRL, 0, 0, buf)) < 0) 
		{
			axerr(dev, "Failed to set PHY reset control: %d", ret);
			goto out2;
		}
	}
	else
	{
		if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_PHY_SELECT, 0x0000, 0, 0, buf)) < 0) 
		{
			axerr(dev, "Select PHY #1 failed: %d", ret);
			goto out2;
		}

		if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_IPPD | AX_SWRESET_PRL, 0, 0, buf)) < 0) 
		{
			axerr(dev, "Failed to power down internal PHY: %d", ret);
			goto out2;
		}
	}

	msleep(150);
	if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, 0x0000, 0, 0, buf)) < 0) 
	{
		axerr(dev, "Failed to reset RX_CTL: %d", ret);
		goto out2;
	}

	/* Get the MAC address. */

	memset(buf, 0, ETH_ALEN);
	if ((ret = ax8817x_read_cmd(dev, AX88772_CMD_READ_NODE_ID, 0, 0, ETH_ALEN, buf)) < 0) 
	{
		axerr(dev, "Failed to read MAC address: %d", ret);
		goto out2;
	}

	memcpy(dev->net->dev_addr, buf, ETH_ALEN);

	if ((ret = ax8817x_write_cmd(dev, AX_CMD_SET_SW_MII, 0, 0, 0, buf)) < 0) 
	{
		axerr(dev, "Enabling software MII failed: %d", ret);
		goto out2;
	}

	if (dev->mii.phy_id == 0x10)
	{
		if ((ret = ax8817x_mdio_read_le(dev->net, dev->mii.phy_id, 2)) != 0x003b) 
		{
			axerr(dev, "Read PHY register 2 must be 0x3b00: %d", ret);
			goto out2;
		}
		
		if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_PRL, 0, 0, buf)) < 0) 
		{
			axerr(dev, "Set external PHY reset pin level: %d", ret);
			goto out2;
		}
		msleep(150);
		if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_IPRL | AX_SWRESET_PRL, 0, 0, buf)) < 0) 
		{
			axerr(dev, "Set Internal/External PHY reset control: %d", ret);
			goto out2;
		}
		msleep(150);
	}


	dev->net->netdev_ops = &ax88x72_netdev_ops;
	dev->net->ethtool_ops = &ax88772_ethtool_ops;

	/* Register suspend and resume functions */
	data->suspend = ax88772_suspend;
	data->resume = ax88772_resume;

	ax8817x_mdio_write_le(dev->net, dev->mii.phy_id, MII_BMCR, BMCR_RESET);
	ax8817x_mdio_write_le(dev->net, dev->mii.phy_id, MII_ADVERTISE, ADVERTISE_ALL | ADVERTISE_CSMA);

	mii_nway_restart(&dev->mii);
        ax772_data->autoneg_start = jiffies;
        ax772_data->Event = WAIT_AUTONEG_COMPLETE;

	if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, AX88772_MEDIUM_DEFAULT, 0, 0, buf)) < 0) 
	{
		axerr(dev, "Write medium mode register: %d", ret);
		goto out2;
	}

	if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_IPG0, AX88772_IPG0_DEFAULT | AX88772_IPG1_DEFAULT << 8, AX88772_IPG2_DEFAULT, 0, buf)) < 0) 
	{
		axerr(dev, "Write IPG,IPG1,IPG2 failed: %d", ret);
		goto out2;
	}
	if ((ret = ax8817x_write_cmd(dev, AX_CMD_SET_HW_MII, 0, 0, 0, &buf)) < 0) 
	{
		axerr(dev, "Failed to set hardware MII: %02x", ret);
		goto out2;
	}

	/* Set RX_CTL to default values with 2k buffer, and enable cactus */
	if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, 0x0088, 0, 0, buf)) < 0) 
	{
		axerr(dev, "Reset RX_CTL failed: %d", ret);
		goto out2;
	}

	/* Asix framing packs multiple eth frames into a 2K usb bulk transfer */
	if (dev->driver_info->flags & FLAG_FRAMING_AX) 
	{
		/* hard_mtu  is still the default - the device does not support
		   jumbo eth frames */
		dev->rx_urb_size = 2048;
	}

	kfree (buf);

    /* Gdy juz wszystko zostalo poprawnie zainicjowane, mozemy utworzyc
     * urzadzenie tekstowe sluzace do komunikacji ze sterowikiem. Umozliwi
     * to ustalanie takich parametrow jak wspolrzedne stacji. */
    if (misc_register(&chardev_mdev))
    {
        axerr(dev, "Create char device /dev/vwlan.\n");
        goto out2;
    }

	printk (version);
	return 0;

out2:
	destroy_workqueue (ax772_data->ax_work);
        kfree (ax772_data);
	kfree(buf);
out1:
	return ret;
}

static void ax88772_unbind(struct usbnet *dev, struct usb_interface *intf)
{
	struct ax8817x_data *data = (struct ax8817x_data *)&dev->data;
	struct ax88772_data *ax772_data = data->priv.ax772_data;

	if (ax772_data) 
	{
		flush_workqueue (ax772_data->ax_work);
		destroy_workqueue (ax772_data->ax_work);

		/* stop MAC operation */
		ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, AX_RX_CTL_STOP, 0, 0, NULL);

		/* Power down PHY */
		ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_IPPD, 0, 0, NULL);

		kfree (ax772_data);
	}
}

static int ax88772_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
{
	u8  *head;
	u32  header;
	char *packet;
	struct sk_buff *ax_skb;
	u16 size;
	int i;
	head = (u8 *) skb->data;
	memcpy(&header, head, sizeof(header));
	le32_to_cpus(&header);
	packet = head + sizeof(header);

	skb_pull(skb, 4);


	if (skb->len > 0){
		size = (u16) (header & 0x0000ffff);
		
		printk("RX FRAME:\n");
		for (i=0; i < size; ++i){
			printk("%02x", packet[i]);
		}
		printk("\n");
	}

	while (skb->len > 0) 
	{
		if ((short)(header & 0x0000ffff) != ~((short)((header & 0xffff0000) >> 16))) 
			axerr(dev, "header length data is error 0x%08x, %d\n", header, skb->len);

		/* get the packet length */
		size = (u16) (header & 0x0000ffff);

		if ((skb->len) - ((size + 1) & 0xfffe) == 0) 
		{
			skb->truesize = size + sizeof(struct sk_buff);
			return 2;
		}

		if (size > ETH_FRAME_LEN) 
		{
			axerr(dev, "invalid rx length %d", size);
			return 0;
		}
		ax_skb = skb_clone(skb, GFP_ATOMIC);
		if (ax_skb) 
		{
			ax_skb->data = packet;

			skb_set_tail_pointer (ax_skb, size);

			ax_skb->truesize = size + sizeof(struct sk_buff);
			usbnet_skb_return(dev, ax_skb);
		} 
		else
			return 0;

		skb_pull(skb, (size + 1) & 0xfffe);

		if (skb->len == 0)
			break;

		head = (u8 *) skb->data;
		memcpy(&header, head, sizeof(header));
		le32_to_cpus(&header);
		packet = head + sizeof(header);
		skb_pull(skb, 4);
	}

	if (skb->len < 0) 
	{
		axerr(dev, "invalid rx length %d", skb->len);
		return 0;
	}
	return 1;
}

static struct sk_buff *ax88772_tx_fixup(struct usbnet *dev, struct sk_buff *skb, unsigned flags)
{
	int padlen;
	int headroom = skb_headroom(skb);
	int tailroom = skb_tailroom(skb);
	u32 packet_len;
	u32 padbytes = 0xffff0000;

	u8  *head;
	u32  header;
	char *packet;
	u16 size;
	int i;
	
	char mac[6] ={ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 };

	head = (u8 *) skb->data;
	memcpy(&header, head, sizeof(header));
	le32_to_cpus(&header);
	packet = head + sizeof(header);
	
	if (skb->len > 0){
		size = (u16) (header & 0x0000ffff);

		skb_copy_to_linear_data_offset(skb, 6,&mac, sizeof(mac));

		printk("TX FRAME:\n");
		for (i=0; i < 20; ++i){
			printk("%02x", packet[i]);
		}
		printk("\n");
	}	

	padlen = ((skb->len + 4) % 512) ? 0 : 4;

	if ((!skb_cloned(skb)) && ((headroom + tailroom) >= (4 + padlen))) 
	{
		if ((headroom < 4) || (tailroom < padlen)) 
		{
			skb->data = memmove(skb->head + 4, skb->data, skb->len);
			skb_set_tail_pointer (skb, skb->len);
		}
	} 
	else 
	{
		struct sk_buff *skb2;
		skb2 = skb_copy_expand(skb, 4, padlen, flags);
		dev_kfree_skb_any(skb);
		skb = skb2;
		if (!skb)
			return NULL;
	}

	skb_push(skb, 4);
	packet_len = (((skb->len - 4) ^ 0x0000ffff) << 16) + (skb->len - 4);
	cpu_to_le32s(&packet_len);

	skb_copy_to_linear_data(skb, &packet_len, sizeof(packet_len));

	if ((skb->len % 512) == 0) 
	{
		memcpy(skb_tail_pointer(skb), &padbytes, sizeof(padbytes));
		skb_put(skb, sizeof(padbytes));
	}

	
	

	return skb;
}

static const u8 ChkCntSel [6][3] = 
{
	{12, 23, 31},
	{12, 31, 23},
	{23, 31, 12},
	{23, 12, 31},
	{31, 12, 23},
	{31, 23, 12}
};

static void ax88772_link_reset (struct work_struct *work)
{
	struct ax88772_data *ax772_data = container_of (work, struct ax88772_data, check_link);
	struct usbnet *dev = ax772_data->dev;
	
	if (ax772_data->Event == AX_SET_RX_CFG) 
	{
		u16 bmcr;
		u16 mode;
		
		ax772_data->Event = AX_NOP;
	
		mode = AX88772_MEDIUM_DEFAULT;

		bmcr = ax8817x_mdio_read_le(dev->net, 
				dev->mii.phy_id, MII_BMCR);
	    if (!(bmcr & BMCR_FULLDPLX))
			mode &= ~AX88772_MEDIUM_FULL_DUPLEX;
		if (!(bmcr & BMCR_SPEED100))
			mode &= ~AX88772_MEDIUM_100MB;

		ax8817x_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, 
			mode, 0, 0, NULL);
		return;
	}
	
	switch (ax772_data->Event) 
	{
	  case WAIT_AUTONEG_COMPLETE:
		if (jiffies > (ax772_data->autoneg_start + 5 * HZ)) 
		{
			ax772_data->Event = PHY_POWER_DOWN;
			ax772_data->TickToExpire = 23;
		}
		break;
	  case PHY_POWER_DOWN:
		if (ax772_data->TickToExpire == 23) 
		{
			/* Set Phy Power Down */
			ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_IPPD, 0, 0, NULL);
			--ax772_data->TickToExpire;
		} 
		else if (--ax772_data->TickToExpire == 0) 
		{
			/* Set Phy Power Up */
			ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_IPRL, 0, 0, NULL);
			ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_IPPD | AX_SWRESET_IPRL, 0, 0, NULL);
			msleep(10);
			ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_IPRL, 0, 0, NULL);
			msleep(60);
			ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_CLEAR, 0, 0, NULL);
			ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_IPRL, 0, 0, NULL);

			ax8817x_mdio_write_le(dev->net, dev->mii.phy_id, MII_ADVERTISE, ADVERTISE_ALL | ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP);
			mii_nway_restart(&dev->mii);
			
			ax772_data->Event = PHY_POWER_UP;
			ax772_data->TickToExpire = 47;
		}
		break;
	  case PHY_POWER_UP:
		if (--ax772_data->TickToExpire == 0) 
		{
			ax772_data->Event = PHY_POWER_DOWN;
			ax772_data->TickToExpire = 23;
		}
		break;
	  default:
		break;
	}
	return;
}

static int ax_suspend (struct usb_interface *intf, pm_message_t message)
{
	struct usbnet *dev = usb_get_intfdata(intf);
	struct ax8817x_data *data = (struct ax8817x_data *)&dev->data;

	return data->suspend (intf, message);
}

static int ax_resume (struct usb_interface *intf)
{
	struct usbnet *dev = usb_get_intfdata(intf);
	struct ax8817x_data *data = (struct ax8817x_data *)&dev->data;

	return data->resume (intf);
}

static const struct driver_info ax88772_info = 
{
	.description = "ASIX AX88772 USB 2.0 Ethernet",
	.bind = ax88772_bind,
	.unbind = ax88772_unbind,
	.status = ax88772_status,
	.flags = FLAG_ETHER | FLAG_FRAMING_AX,
	.rx_fixup = ax88772_rx_fixup,
	.tx_fixup = ax88772_tx_fixup,
	.data = 0x00130103,
};

static const struct driver_info dlink_dub_e100b_info = 
{
	.description = "D-Link DUB-E100 USB 2.0 Fast Ethernet Adapter",
	.bind = ax88772_bind,
	.unbind = ax88772_unbind,
	.status = ax88772_status,
	.flags = FLAG_ETHER | FLAG_FRAMING_AX,
	.rx_fixup = ax88772_rx_fixup,
	.tx_fixup = ax88772_tx_fixup,
	.data = 0x00130103,
};

static const struct usb_device_id products [] = 
{
	{
		// DLink DUB-E100B
		USB_DEVICE (0x2001, 0x3c05),
		.driver_info =  (unsigned long) &dlink_dub_e100b_info,
	}, 

	{
		// DLink DUB-E100B
		USB_DEVICE (0x07d1, 0x3c05),
		.driver_info =  (unsigned long) &dlink_dub_e100b_info,
	}, 

	{
		// ASIX AX88772 10/100
 	       USB_DEVICE (0x0b95, 0x7720),
 	       .driver_info = (unsigned long) &ax88772_info,
	}, 

	{ 
		// END
	}, 
};

MODULE_DEVICE_TABLE(usb, products);

static struct usb_driver asix_driver = 
{
	.name =		"asix",
	.id_table =	products,
	.probe =	usbnet_probe,
	.suspend =	ax_suspend,
	.resume =	ax_resume,
	.disconnect =	usbnet_disconnect,
};

static int __init asix_init(void)
{
    frame_end = frame;
 	return usb_register(&asix_driver);
}

module_init(asix_init);

static void __exit asix_exit(void)
{
 	usb_deregister(&asix_driver);

    /* Po zamknięciu sterownika karty możemy usunąć urządzenie tekstowe. */
    misc_deregister(&chardev_mdev);
}

module_exit(asix_exit);

MODULE_AUTHOR("David Hollis");
MODULE_DESCRIPTION("ASIX AX8817X based USB 2.0 Ethernet Devices");
MODULE_LICENSE("GPL");
